#include <stdio.h>
#include <stdlib.h>
#include "lwip/apps/httpd.h"
#include "lwip/apps/httpd_opts.h"
#include "lwip/def.h"
#include "lwip/apps/fs.h"
#include <string.h>
#include "httpd_post.h"
#include "httpd_post_values.h"

#define WINDOW_SIZE 240
// typedef void (*key_value_proc)(char *name, int name_len, char *value, int value_len);
enum {
    POST_STATE_BOUNDARY,
    POST_STATE_NAME,
    POST_STATE_CRLF,
    POST_STATE_DATA,
    POST_STATE_END,
};

typedef struct _httpd_post_context {
    struct _httpd_post_context *next;
    char boundary[60];
    int boundary_len;
    char uri[60];
    char name[50];
    char window_data[WINDOW_SIZE];
    int window_len;
    int state_mechine;
    void *connection;
} httpd_post_context;

httpd_post_context *g_httpd_post_context_head;

static httpd_post_context *http_post_malloc_context(void *connection)
{ 
    httpd_post_context *context = malloc(sizeof(httpd_post_context));

    context->connection = connection;

    memset(context->boundary, 0, sizeof(context->boundary));
    context->boundary_len = 0;
    memset(context->uri, 0, sizeof(context->uri));
    memset(context->name, 0, sizeof(context->name));
    memset(context->window_data, 0, sizeof(context->window_data));
    strcpy(context->boundary, "--");
    context->window_len = 0;
    context->state_mechine = POST_STATE_BOUNDARY;

    context->next = g_httpd_post_context_head;
    g_httpd_post_context_head = context;
    return context;
}

static void httpd_post_free_context(void *connection)
{
    httpd_post_context *temp = g_httpd_post_context_head;

    if (temp->connection == connection) {
        g_httpd_post_context_head = temp->next;
        free(temp);
        return;
    }
    while (temp != NULL) { 
        if (temp->next != NULL && temp->next->connection == connection) {
            temp->next = temp->next->next;
            free(temp->next);
            return;
        }
        temp = temp->next;
    }
}

static httpd_post_context *http_post_find_context(void *connection)
{
    httpd_post_context *temp = g_httpd_post_context_head;

    while (temp != NULL) { 
        if (temp->connection == connection) {
            return temp;
        }
        temp = temp->next;
    }
    return NULL;
}

static void httpd_post_move_window(httpd_post_context *context, int len)
{
    memmove(context->window_data, context->window_data + len, context->window_len - len);
    context->window_len -= len;
    context->window_data[context->window_len] = 0;
}

static err_t httpd_post_get_key_value(const char *key, const char *http_request, char *buffer, uint16_t len)
{
    char *pos;
    int i;

    pos = strstr(http_request, key);
    if (pos == NULL) {
        return ERR_VAL;
    }
    memset(buffer, 0, len);
    pos += strlen(key);
    for (i = 0; i < len; i++) {
        if (pos[i] == '\r') {
            break;
        }
        buffer[i] = pos[i];
    }
    return ERR_OK;
}

err_t httpd_post_begin(void *connection, const char *uri, const char *http_request,
                       uint16_t http_request_len, int content_len, char *response_uri,
                       uint16_t response_uri_len, uint8_t *post_auto_wnd)
{
    char boundary[60] = "--";

    if (httpd_post_get_key_value("boundary=", http_request, boundary + 2, sizeof(boundary) - 2) != ERR_OK) {
        return ERR_VAL;
    }

    httpd_post_context *context = http_post_malloc_context(connection);

    strncpy(context->uri, uri, sizeof(context->uri));
    context->boundary_len = strlen(boundary);
    strncpy(context->boundary, boundary, sizeof(context->boundary));

    httpd_post_value_init(context->uri);    
    return ERR_OK;
}

void httpd_post_parse_window(httpd_post_context *context)
{
    char *pos;
    int len;

    if (context->window_len == 0) {
        return;
    }
    if (context->state_mechine == POST_STATE_BOUNDARY) {
        if (context->window_len < context->boundary_len) {
            return;
        }
        httpd_post_move_window(context, context->boundary_len);
        context->state_mechine = POST_STATE_NAME;
    }

    if (context->state_mechine == POST_STATE_NAME) {
        if (context->window_len < 10) {
            return;
        }
        pos = strstr(context->window_data, "filename=\"");
        while ((pos == NULL) || (pos - context->window_data > 10)) {
            httpd_post_move_window(context, 10);
            pos = strstr(context->window_data, "filename=\"");
        }
        pos += 10;
        len = 0;
        while (*pos != '"') {
            context->name[len++] = *pos;
            pos++;
            if (pos >= context->window_data + context->window_len) {
                return;
            }
        }
        httpd_post_move_window(context, pos - context->window_data);
        context->state_mechine = POST_STATE_CRLF;
    }
    if (context->state_mechine == POST_STATE_CRLF) {
        pos = strstr(context->window_data, "\r\n\r\n");
        if (pos == NULL) {
            return;
        }
        httpd_post_move_window(context, pos - context->window_data + 4);
        context->state_mechine = POST_STATE_DATA;
        return;
    }
    pos = strstr(context->window_data, context->boundary);
    if (pos) {
        httpd_post_value_set(context->uri, context->name, context->window_data, pos - context->window_data - 2);
        context->window_len = 0;
        return;
    }
    len = context->window_len;
    if (len > sizeof(context->window_data) / 3) {
        len = sizeof(context->window_data) / 3;
    }
    httpd_post_value_set(context->uri, context->name, context->window_data, len);
    httpd_post_move_window(context, len);
}

void httpd_push_to_window(httpd_post_context *context)
{
    int thld = sizeof(context->window_data) * 2 / 3;

    if (context->window_len > thld) {
        httpd_post_parse_window(context);
    }
}

static void httpd_post_parse_data(void *connection, char *buffer, int len)
{
    int temp_len;
    httpd_post_context *context = http_post_find_context(connection);

    if (context == NULL) {
        return;
    }
    while (len) {
        temp_len = len;
        if (context->window_len + temp_len > sizeof(context->window_data) - 1) {
            temp_len = sizeof(context->window_data) - 1 - context->window_len;
        }
        memcpy(context->window_data + context->window_len, buffer, temp_len);
        context->window_len += temp_len;
        buffer += temp_len;
        len -= temp_len;
        context->window_data[context->window_len] = 0;
        httpd_push_to_window(context);
    }
}

err_t httpd_post_receive_data(void *connection, struct pbuf *p)
{
    struct pbuf *q = p;

    while(q != NULL)  /* 缓存接收的数据至http_post_payload */
    {
        httpd_post_parse_data(connection, q->payload, q->len);
        q = q->next;
    }
    pbuf_free(p); /* 释放pbuf */
    return ERR_OK;
}

void httpd_post_finished(void *connection, char *response_uri, uint16_t response_uri_len)
{
    httpd_post_context *context = http_post_find_context(connection);

    if (context) {
        for (int i = 0; i < 10; i++) {
            if (context->window_len == 0) {
                break;
            }
            httpd_post_parse_window(context);
        }
        httpd_post_free_context(context);
    }
    httpd_post_value_finished(response_uri, response_uri_len);     
}
